package org.ectar.papi
{
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.system.Security;
	
	import mx.collections.ArrayCollection;
	import mx.messaging.messages.HTTPRequestMessage;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;
	
	import org.ectar.papi.data.GAlbum;
	import org.ectar.papi.data.GBBox;
	import org.ectar.papi.data.GEntry;
	import org.ectar.papi.data.GPhoto;
	import org.ectar.papi.event.GDataEvent;

	/**
	 * 
	 * Data Service which will work with Google via HTTP<br/>
	 * Contains HTPService inside and set of wrappers which will hep you to do simple data requests.
	 * 
	 * <br/>Example of use:<br/><br/>
	 * 
	 *	<code>gService.userName = username;</code><br/>
	 *	<code>gService.proxied = true;</code><br/>
	 *	<code>gService.projection = GDataService.GDATA_PROJECTION_API;</code><br/>
	 *	<code>gService.getAlbum("Your Album name");</code><br/>
	 *	<code>gService.addEventListener(GDataEvent.GET_ALBUM,handleAlbum);</code><br/><br/>
	 * 
	 * where <code>handleAlbum</code> callback function which basically handles ResultEvent from internal HTTP service.<br/>
	 * and <code>gService.userName</code> is your gallery user name, and where "Your Album name" is the string name of your album. 

  	 * @author Mykhaylo Lytvynyuk
  	 * 
	 * */
	public class GDataService extends EventDispatcher	
	{
		/** Specifies type of projection API */
		public static const GDATA_PROJECTION_API:String = "api";
		/** Specifies type of projection BASE */
		public static const GDATA_PROJECTION_BASE:String = "base";

		private static const GDATA_SERVICE_URL:String = "http://photos.googleapis.com/data/feed/"
		private static const GDATA_SERVICE_URL2:String = "http://picasaweb.google.com/data/feed/"
		private static const GDATA_SERVICE_PROXY_URL:String = "http://picasafx.com/proxy.php?url="
		private static const GDATA_IMAGE_PROXY_URL:String = "http://picasafx.com/loadImage.php?file=" 
		private static const GDATA_COMMUNITY:String = "/all";
		private static const GDATA_USER_PREFFICS:String = "/user/";
		private static const GDATA_ALBUM_PREFFICS:String = "/album/";
		private static const GDATA_ALBUMID_PREFFICS:String = "/albumid/";
		private static const GDATA_PHOTOID_PREFFICS:String = "/photoid/";
		private static const GDATA_COMMENTID_PREFFICS:String = "/commentid/";		
		
		private static const GDATA_PHOTO_KIND:String = "?kind=photo";
		private static const GDATA_ALBUM_KIND:String = "?kind=album";
		private static const GDATA_COMMENT_KIND:String = "?kind=comment";
		
		private static const GDATA_STARTINDEX_PARAMETER:String = "start-index";
		private static const GDATA_MAXRESULTS_PARAMETER:String = "max-results";
		private static const GDATA_KIND_PARAMETER:String = "kind";
		private static const GDATA_ACCESS_PARAMETER:String = "access";
		private static const GDATA_QUERY_PARAMETER:String = "q";
		private static const GDATA_BBOX_PARAMETER:String = "bbox";
		private static const GDATA_FIELDS_PARAMETER:String = "fields";
		
		private static const GDATA_VERSION_HEADER:String = "GData-Version";
		private static const GDATA_VERSION_URI:String = "v";
		private static const GDATA_VERSION_ONE:String = "1";
		private static const GDATA_VERSION_TWO:String = "2";
		
		/* Authentication related constants */
		private static var GDATA_AUTH_URL:String = "https://www.google.com/accounts/ClientLogin";
		
		/**
		 * 	Type of account to be authenticated. Possible values are:
			GOOGLE (authenticate as a Google account only)
			HOSTED (authenticate as a hosted account only)
			HOSTED_OR_GOOGLE (authenticate first as a hosted account; if attempt fails, authenticate as a Google account)
			Use HOSTED_OR_GOOGLE if you're not sure which type of account needs authentication. If the user information matches both a hosted and a Google account, only the hosted account is authenticated.  
		 */
		private static const GDATA_AUTH_ACCOUNTTYPE_PARAMETER:String = "accountType";
		/**
		 * GOOGLE (authenticate as a Google account only)
		 */
		private static const GDATA_AUTH_ACCOUNTTYPE_GOOGLE:String = "GOOGLE";
		/**
		 * HOSTED (authenticate as a hosted account only)
		 */
		private static const GDATA_AUTH_ACCOUNTTYPE_HOSTED:String = "HOSTED";
		/**
		 * HOSTED_OR_GOOGLE (authenticate first as a hosted account; if attempt fails, authenticate as a Google account)
		 */
		private static const GDATA_AUTH_ACCOUNTTYPE_HOSTED_OR_GOOGLE:String = "HOSTED_OR_GOOGLE";						
		
		/**
		 * User's full email address. It must include the domain (i.e. johndoe@gmail.com). 
		 */
		private static const GDATA_AUTH_EMAIL_PARAMETER:String = "Email";
		
		/**
		 * User's password.
		 */
		private static const GDATA_AUTH_PASSWORD_PARAMETER:String = "Passwd";
		
		/**
		 * Name of the Google service you're requesting authorization for. 
		 * Each service using the Authentication service is assigned a name value; 
		 * for example, the name associated with Google Calendar is 'cl'. 
		 * This parameter is required when accessing services based on Google data APIs; 
		 * in this case, the generic service name "xapi" can be used. 
		 * For specific service names, refer to the service documentation.
		 * 
		 * Note: for Picasa API: lh2
		 */
		private static const GDATA_AUTH_SERVICE_PARAMETER:String = "service";
		private static const GDATA_AUTH_PICASA_SERVICE_NAME:String = "lh2";		
		
		/**
		 * Short string identifying your application, for logging purposes.
		 * This string should take the form: "companyName-applicationName-versionID".
		 */
		private static const GDATA_AUTH_SOURCE_PARAMETER:String = "source";

		/**
		 * (optional) Token representing the specific CAPTCHA challenge. 
		 * Google supplies this token and the CAPTCHA image URL in a login failed 
		 * response with the error code "CaptchaRequired". 
		 */
		private static const GDATA_AUTH_LOGINTOKEN_PARAMETER:String = "logintoken";		
		
		/**
		 * (optional) String entered by the user as an answer to a CAPTCHA challenge.
		 */
		private static const GDATA_AUTH_LOGINCAPT_PARAMETER:String = "logincaptcha";		
		
		private static const GDATA_AMP:String = "&";
		private static const GDATA_AMP_SAFE:String = "%26";
		
		private static const LOCAL:int = 0;
		private static const REMOTE:int = 1;
		
		/** Authentication token */
		private var AUTH:String;
		
		/** authenticated user name */
		private var authUserName:String;
		
		/** Hardcoded to 2 */
		private var _version:String = GDATA_VERSION_TWO;	
		
		/**
		 * Constructor, creates instance of GDataService
		 * Initialises  HTTPService.
		 */
		public function GDataService():void {
			service = new HTTPService();
			
			service.useProxy = false;
			
			/*
			if(!proxied)
				Security.loadPolicyFile(policyFile);
			*/
			
			/* Handle API version */
			if(version == GDATA_VERSION_TWO)
				service.headers = {"GData-Version":GDATA_VERSION_TWO};
			else
				service.headers = {"GData-Version":GDATA_VERSION_ONE};
		}
		
		private var _userName:String;
		
		private var authenticated:Boolean;
		
		private var _requestURL:String;

		public var service:HTTPService;
		
		private var _projection:String;
		
		private var _deployment:int;
		
		private static var policyFile:String = "http://photos.googleapis.com/data/crossdomain.xml";
				
		[Bindable] private var apm:String = GDATA_AMP;
		
		[Bindable] private var _proxied:Boolean = false;
		/**
		 * Sets the type of deployment of Aplication. 
		 * Proxy is simple redirector which redirects all query from application to googledata services
		 * This is necessary workaround to get away from problem with Google Data crossdomain.xml restrictions
		 * 
		 * proxied = false - not require proxy
		 * proxied = true - require proxy;
		 * */
		public function set proxied(value:Boolean):void {
			_proxied = value;
			apm = _proxied?GDATA_AMP_SAFE:GDATA_AMP
		}

		/**
		 * Indicates the type of deployment of Aplication. 
		 * Proxy is simple redirector which redirects all query from application to googledata services
		 * This is necessary workaround to get away from problem with Google Data crossdomain.xml restrictions
		 * 
		 * proxied = false - not require proxy
		 * proxied = true - require proxy;
		 * */		
		public function get proxied():Boolean {
			return _proxied;
		}
		
		/**
		 * Set the user name for current GDataService instance
		 * */
		public function set userName(value:String):void {
			_userName = value;		
		}
		
		/**
		 * Returns user name of current GDataService instance
		 * */
		public function get userName():String {
			return _userName;
		}
		
		/**
		 * Specifies type of projection for current GDataService instance
		 * Can be "api" or "base"
		 * */
		public function set projection(value:String):void {
			_projection = value;		
		}
		
		/**
		 * Returns type of projection of GDataService instance
		 * */
		public function get projection():String {
			return _projection;
		}
		
		/**
		 * Return current version of API calls
		 * */
		public function get version():String {
			return _version;
		}
		
		/**
		 * Switches the version (Doesnt work YET)
		 * 
		 * TODO: Implement switching? Do we really need it?
		 * */
		public function set version(value:String):void {
		}

		/**
		 * Wrapper function which does real HTTP request and return result to user specified callback function
		 * This function request a feed which contains all photos in the specified album and complete Album definition
		 * 
		 * Fires <code>GET_ALBUM</code> event in case data available
		 * and <code>GET_ALBUM_FAILED</code> in case parameters are wrong and no data available or in case communication error
		 * 
		 * @param albumName  - String represents album name
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 * */
		public function getAlbum(albumName:String, startIndex:int = 0, maxResults:int = -1,fields:String=null):void {
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection 
							+ GDATA_USER_PREFFICS + userName 
							+ GDATA_ALBUM_PREFFICS 
							+ albumName + GDATA_PHOTO_KIND + 
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) +
							(startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);

			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url;
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onResult);
			service.addEventListener(FaultEvent.FAULT, onFailed);			

			service.send();
			
			// kinda hack to have reference to GDataServices :)
			var eventDispatcher:* = this;
			
			function onResult(event:ResultEvent):void {
	
				service.removeEventListener(ResultEvent.RESULT, onResult);
				service.removeEventListener(FaultEvent.FAULT, onFailed);
								
				var result:XML = XML(event.result);
				var album:GAlbum = GDataHelper.parseAlbum(result);
				
				if(album) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_ALBUM,album);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_ALBUM_FAILED));
				}
			}
	
			function onFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onResult);
				service.removeEventListener(FaultEvent.FAULT, onFailed);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_ALBUM_FAILED,event));
			}
		}
		
		/**
		 * Wrapper function which does real HTTP request and return result to user specified callback function
		 * This function request a feed which contains all photos in the specified album.
		 * 
		 * Fires <code>GET_PHOTOS_BY_ALBUM</code> event in case data available
		 * and <code>GET_PHOTOS_BY_ALBUM_FAILED</code> in case parameters are wrong and no data available or in case of communication error
		 * 
		 * @param albumName  - String represents album name
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 * */
		public function getPhotosByAlbum(albumName:String, startIndex:int = 0, maxResults:int = -1,fields:String=null):void {
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection 
							+ GDATA_USER_PREFFICS + userName 
							+ GDATA_ALBUM_PREFFICS 
							+ albumName + GDATA_PHOTO_KIND + 
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) +
							(startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);
							
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
							
			service.url = url;
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onGetPhotosByAlbumResult);
			service.addEventListener(FaultEvent.FAULT, onGetPhotosByAlbumFailed);			

			service.send();
			
			var eventDispatcher:* = this;
			
			function onGetPhotosByAlbumResult(event:ResultEvent):void {
	
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByAlbumResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByAlbumFailed);
								
				var result:XML = XML(event.result);
				var photos:ArrayCollection = GDataHelper.parsePhotos(result);
				
				if(photos) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_PHOTOS_BY_ALBUM,photos);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTOS_BY_ALBUM_FAILED));
				}
			}
		
			function onGetPhotosByAlbumFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByAlbumResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByAlbumFailed);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTOS_BY_ALBUM_FAILED,event));
			}
		}
		
		/**
		 * Wrapper function which does real HTTP request and return result to user specified callback function
		 * This function request a feed which contains an album content for given albumId.

		 * Fires <code>GET_PHOTOS_BY_ALBUM</code> event in case data available
		 * and <code>GET_PHOTOS_BY_ALBUM_FAILED</code> in case parameters are wrong and no data available or in case of communication error
  
		 * @param albumID - an album ID
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 */
		public function getPhotosByAlbumId(albumID:String, startIndex:int = 0, maxResults:int = -1,fields:String=null):void {
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection 
							+ GDATA_USER_PREFFICS + userName + GDATA_ALBUMID_PREFFICS 
							+ albumID + GDATA_PHOTO_KIND + 
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) +
							(startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);							

			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url; 
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onGetPhotosByAlbumIdResult);
			service.addEventListener(FaultEvent.FAULT, onGetPhotosByAlbumIdFailed);
			
			service.send();

			var eventDispatcher:* = this;
			
			function onGetPhotosByAlbumIdResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByAlbumIdResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByAlbumIdFailed);
				
				var result:XML = XML(event.result);
				var photos:ArrayCollection = GDataHelper.parsePhotos(result);
				
				if(photos) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_PHOTOS_BY_ALBUM,photos);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTOS_BY_ALBUM_FAILED));
				}				
			}

			function onGetPhotosByAlbumIdFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByAlbumIdResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByAlbumIdFailed);
								
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTOS_BY_ALBUM_FAILED,event));
			}
		}
		
		/**
		 * Wrapper function which does real HTTP request and return result to user specified callback function
		 * This function request a feed which contains all photo details by photoId iin to specified album by album ID
		 * If photo isn't public function return nothing

		 * Fires <code>GET_PHOTO</code> event in case data available
		 * and <code>NO_DATA_AVAILABLE</code> in case parameters are wrong and no data available 
		 * and <code>GET_PHOTO_FAILED</code> in case of communication error
   
		 * @param photoId - an ID of photo
		 * @param albumId - an ID of album
		 * 
		 */
		public function getPhotoById(photoId:String, albumId:String,fields:String=null):void {
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection + GDATA_USER_PREFFICS + userName + GDATA_ALBUMID_PREFFICS 
							+ albumId + GDATA_PHOTOID_PREFFICS + photoId +
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) + "?imgmax=800";

			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url;
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onGetPhotoByIdResult);
			service.addEventListener(FaultEvent.FAULT, onGetPhotoByIdFailed);
			
			service.send();
			
			var eventDispatcher:* = this;
			
			function onGetPhotoByIdResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotoByIdResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotoByIdFailed);
				
				var result:XML = XML(event.result);
				var photo:GEntry = GDataHelper.parsePhoto(result);
				
				if(photo) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_PHOTO,photo);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.NO_DATA_AVAILABLE));
				}
			}
	
			function onGetPhotoByIdFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotoByIdResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotoByIdFailed);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTO_FAILED,event));
			}
		}

		/**
		 * Wrapper function which does real HTTP request and return result to user specified callback function
		 * This function requests feed which contains all possible albums for given user.
		 * For now this function returns a list of only Public albums

		 * Fires <code>GET_ALBUMS</code> event in case data available
		 * and <code>NO_DATA_AVAILABLE</code> in case parameters are wrong and no data available 
		 * and <code>GET_ALBUMS_FAILED</code> in case of communication error
   
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 * */		
		public function getAlbumsForCurrentUser(startIndex:int = 0, maxResults:int = -1,fields:String=null):void {
			
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection 
							+ GDATA_USER_PREFFICS + userName + "/" + GDATA_ALBUM_KIND
							+ (startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);
			
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
											
			service.url = url;
							
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onGetAlbumsForCurrentUserResult);
			service.addEventListener(FaultEvent.FAULT, onGetAlbumsForCurrentUserFailed);			
			
			service.send();
			
			var eventDispatcher:* = this;
			
			function onGetAlbumsForCurrentUserResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetAlbumsForCurrentUserResult);
				service.removeEventListener(FaultEvent.FAULT, onGetAlbumsForCurrentUserFailed);
				
				var result:XML = XML(event.result);
				var albums:ArrayCollection = GDataHelper.parseAlbums(result);
				
				if(albums) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_ALBUMS,albums);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.NO_DATA_AVAILABLE));
				}
			}

			function onGetAlbumsForCurrentUserFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetAlbumsForCurrentUserResult);
				service.removeEventListener(FaultEvent.FAULT, onGetAlbumsForCurrentUserFailed);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_ALBUMS_FAILED,event));
			}
		}
		
		/**
		 * Wrapper function which does real HTTP request and return result to user specified callback function
		 * This function requests feed which contains search result specifies in searchTerm. 
		 * Set of resulting pictures can be limited by <code>startIndex</code> and <code>maxResults</code>
		 * this function returns a list of only Public pictures
		 *  
		 * @param searchTerm - search string which is term for image search
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * @param fields - supoort partial parameter retrieval; NEW IN VERSION 2
		 * 
		 * */
		public function getPhotosByTerm(searchTerm:String, startIndex:int = 0, maxResults:int = -1,fields:String=null):void {
			
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection + 
							GDATA_COMMUNITY + GDATA_PHOTO_KIND + 
							apm + GDATA_QUERY_PARAMETER + "=" + searchTerm + 
							(fields!=null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) +
							(startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);
			
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url;							
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onGetPhotosByTermResult);
			service.addEventListener(FaultEvent.FAULT, onGetPhotosByTermFailed);
			
			service.send();

			var eventDispatcher:* = this;
			
			function onGetPhotosByTermResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByTermResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByTermFailed);
				
				var result:XML = XML(event.result);
				var photos:ArrayCollection = GDataHelper.parsePhotos(result);
				
				if(photos) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_PHOTOS_BY_TERM,photos);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.NO_DATA_AVAILABLE));
				}				
			}
	
			function onGetPhotosByTermFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByTermResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByTermFailed);				
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTOS_BY_TERM_FAILED,event));
			}
		}
		
		/**
		 * Wrapper function which does HTTP request and return result to user specified callback function
		 * This function requests feed which contains set of pictures by specified GEO coordinates. 
		 * Set of resulting pictures can be limited by <code>startIndex</code> and <code>maxResults</code>
		 * this function returns a list of only Public pictures
		 *  
		 * @param searchTerm - search string which is term for image search, can be "" if no search term needed
		 * @param boundingBox - are with we shoul check for pictures
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 * WARNING: Function not tested, dont use it yet!
		 * 
		 * */
		public function getPhotosByGEO(searchTerm:String, bbox:GBBox, startIndex:int = 0, maxResults:int = -1,fields:String=null):void {
			
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection + 
							GDATA_COMMUNITY + GDATA_PHOTO_KIND + 
							apm + (searchTerm?(GDATA_QUERY_PARAMETER + "=" + searchTerm):"") +
							apm + GDATA_BBOX_PARAMETER + "=" + bbox.toString() + 
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) +
							(startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);
			
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url;
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = HTTPService.RESULT_FORMAT_E4X;
			
			service.addEventListener(ResultEvent.RESULT, onGetPhotosByGEOResult);
			service.addEventListener(FaultEvent.FAULT, onGetPhotosByGEOFailed);
			
			service.send();

			var eventDispatcher:* = this;
			
			function onGetPhotosByGEOResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByGEOResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByGEOFailed);
				
				var result:XML = XML(event.result);
				var photos:ArrayCollection = GDataHelper.parsePhotos(result);
				
				if(photos) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_PHOTOS_BY_GEO,photos);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.NO_DATA_AVAILABLE));
				}				
			}
	
			function onGetPhotosByGEOFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetPhotosByGEOResult);
				service.removeEventListener(FaultEvent.FAULT, onGetPhotosByGEOFailed);				
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_PHOTOS_BY_GEO_FAILED,event));
			}
		}
		
		/**
		 * This function requests feed which contains all comment for fiven photoId. 
		 * Set of resulting pictures can be limited by <code>startIndex</code> and <code>maxResults</code>
		 * this function returns a list of only Public pictures
		 *  
		 * @param photoId - an Id of the photo
		 * @param albumId - albumId where photo belongs to
		 * @param startIndex - offset from the beginning of the list
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 * WARNING: Function not tested, dont use it yet!
		 * 
		 * */
		public function getCommentsByPhotoId(photoId:String,albumId:String, startIndex:int = 0, maxResults:int = -1,fields:String=null):void {

			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection 
							+ GDATA_USER_PREFFICS + userName + GDATA_ALBUMID_PREFFICS 
							+ albumId + GDATA_PHOTOID_PREFFICS + photoId
							+ GDATA_COMMENT_KIND + 
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields) +
							(startIndex==0?"":apm + GDATA_STARTINDEX_PARAMETER + "=" + startIndex) + 
							(maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);							
			
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url; 
			
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = HTTPService.RESULT_FORMAT_E4X;
			
			service.addEventListener(ResultEvent.RESULT, onGetCommentsByPhotoIdResult);
			service.addEventListener(FaultEvent.FAULT, onGetCommentsByPhotoIdFailed);
			
			service.send();
			
			var eventDispatcher:* = this;
			
			function onGetCommentsByPhotoIdResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetCommentsByPhotoIdResult);
				service.removeEventListener(FaultEvent.FAULT, onGetCommentsByPhotoIdFailed);
				
				var result:XML = XML(event.result);
				var comments:ArrayCollection = GDataHelper.parseComments(result);
				
				if(comments) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_COMMENTS_FOR_PHOTO,comments);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.NO_DATA_AVAILABLE));
				}				
			}
	
			function onGetCommentsByPhotoIdFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetCommentsByPhotoIdResult);
				service.removeEventListener(FaultEvent.FAULT, onGetCommentsByPhotoIdFailed);				
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_COMMENTS_FOR_PHOTO_FAILED,event));
			}
		}
		
		/**
		 * Authentication with ClientLogin method
		 * This method suppose to be used only with application installed on local computer
		 * For web app better to use AuthSub method which is not that excited because redirects
		 * web app to google login page.
		 * 
		 * Still this method will provide authentication, but in that case Web app shoul deal with 
		 * collecting user login information, which can be inapropriate in some cases because
		 * of security and privacy issues.
		 * 
		 * @param email - user email e.g. johnsmith@gmail.com
		 * @param password - user password, unecripted
		 * @param type - authentication type, see <code>GDATA_AUTH_ACCOUNTTYPE_GOOGLE, GDATA_AUTH_ACCOUNTTYPE_HOSTED, GDATA_AUTH_ACCOUNTTYPE_HOSTED_OR_GOOGLE </code>
		 * 
		 */
		public function authenticateClientApp(email:String, password:String, type:String = GDATA_AUTH_ACCOUNTTYPE_GOOGLE):void {
			var url:String = GDATA_AUTH_URL;
			
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url;
			
			service.method = HTTPRequestMessage.POST_METHOD;
			
			var parameters:Object = new Object();
			
			parameters[GDATA_AUTH_ACCOUNTTYPE_PARAMETER] = type;
			parameters[GDATA_AUTH_EMAIL_PARAMETER] = email;
			parameters[GDATA_AUTH_PASSWORD_PARAMETER] = password;
			parameters[GDATA_AUTH_SERVICE_PARAMETER] = GDATA_AUTH_PICASA_SERVICE_NAME;
			parameters[GDATA_AUTH_SOURCE_PARAMETER] = source;

			service.resultFormat = HTTPService.RESULT_FORMAT_TEXT;
			
			service.addEventListener(ResultEvent.RESULT, authOk);
			service.addEventListener(FaultEvent.FAULT, authFailed);
			
			service.send(parameters);
			
			var eventDispatcher:* = this;
			
			// used to extract SID token and store it locally for future references
			function authOk(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, authOk);
				
				var result:Array = (event.result as String).split("\n");
				
				AUTH = result[2];
				
				if(AUTH && AUTH.length != 0)
					authenticated = true;
				
				authUserName = email;

				service.removeEventListener(ResultEvent.RESULT, authOk);
				service.removeEventListener(FaultEvent.FAULT, authFailed);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.USER_AUTHENTICATED));
			}
			
			function authFailed(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, authOk);
				service.removeEventListener(FaultEvent.FAULT, authFailed);
								
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.USER_AUTHENTICATION_FAILED));
			}
		}
		
		/** not yeat impleneted, because I dont like when my app redirected to google login page
		 * and then back */
		public function authenticateWebApp(email:String, password:String, callbackResult:Function, callbackFault:Function, type:String = GDATA_AUTH_ACCOUNTTYPE_GOOGLE):void {
			var url:String = GDATA_AUTH_URL;
			
			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
			
			service.url = url;
			
			service.method = HTTPRequestMessage.POST_METHOD;
			
			var parameters:Object = new Object();
			
			parameters[GDATA_AUTH_ACCOUNTTYPE_PARAMETER] = type;
			parameters[GDATA_AUTH_EMAIL_PARAMETER] = email;
			parameters[GDATA_AUTH_PASSWORD_PARAMETER] = password;
			parameters[GDATA_AUTH_SERVICE_PARAMETER] = GDATA_AUTH_PICASA_SERVICE_NAME;
			parameters[GDATA_AUTH_SOURCE_PARAMETER] = source;

			service.resultFormat = HTTPService.RESULT_FORMAT_TEXT;
			
			service.addEventListener(ResultEvent.RESULT, authOk);
			service.addEventListener(FaultEvent.FAULT, authFailed);
			
			service.send(parameters);
			
			var eventDispatcher:* = this;
			
			// used to extract SID token and store it locally for future references
			function authOk(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, authOk);
				
				var result:Array = (event.result as String).split("\n");
				
				AUTH = result[0];
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.USER_AUTHENTICATED));				
			}
			
			function authFailed(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, authOk);
				service.removeEventListener(FaultEvent.FAULT, authFailed);
								
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.USER_AUTHENTICATION_FAILED));
			}			
		}
		
		/** to set GDATA_AUTH_SOURCE_PARAMETER parameter*/
		public function set source(value:String):void {
		}
		
		/** to get GDATA_AUTH_SOURCE_PARAMETER parameter*/
		public function get source():String {
			return "blah-blah-1.0";
		}
		
		/** Update/Create/Delete Functions */
		
		/**
		 * Function creates album, required album descriptot
		 *  
		 * @param albumName
		 * 
		 */
		 
		/**
		 * Sends request for creating an album. All required fueld must be filled in albumDescriptor.
		 * 
		 * @param albumDescriptor - instance of GAlbum, but only requited fields are populated.
		 * 				<code>*title, summary, 
		 * 				gphoto.location, *gphoto.access , *gphoto.commentingEnabled, *gphoto.timestamp
		 * 				media.keywords
		 * 				category</code>
		 * '*' - means mandatory fields, functionn doesnt do any validatio.
		 * 
		 * @throws <code>GDATA_INVALID_PARAMETER</code> exception
		 * 
		 * TODO: implement proper validation.
		 * 
		 */		 
		public function createAlbum(albumName:String):void {
			
			if(authenticated) {
				
				var album:GAlbum = new GAlbum();
				album.title = "test";
				album.gphoto.access = GPhoto.GPHOTO_ACCESS_PUBLIC;
				album.gphoto.commentingEnabled = false; //? not sure
				
				service.addEventListener(ResultEvent.RESULT, resultOK);
				service.addEventListener(FaultEvent.FAULT, fault);
				
				service.method = HTTPRequestMessage.POST_METHOD;
				
				service.headers["Authorization"]  = "GoogleLogin " + AUTH;

				var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + GDATA_PROJECTION_API 
								+ GDATA_USER_PREFFICS + authUserName;
			    
				url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
				
			    service.url = url;
			                
                try {
                	service.contentType = "application/atom+xml";
                	service.send(GDataGenerator.creatAlbumAtomDescriptorXML(album));
                } catch (error:Error) {
                    dispatchEvent(new GDataEvent(GDataEvent.UNABLE_TO_SEND));
                }
			} else {
				throw new Error(GDataEvent.NOT_AUTHENTICATED)
			}
			
			// kinda hack to have reference to GDataServices :)
			var eventDispatcher:* = this;
			
			function resultOK(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);

				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.ALBUM_ADDED));
			}
			
			function fault(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);

				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.IO_ERROR));
			}
		}
			
		/**
		 * Function deletes certain album, required albumId and album version
		 * 
		 * TODO: Implement all features properly
		 * ATTENTION: Not finished
		 *  
		 * @param albumId
		 * 
		 */
		public function deleteAlbum(albumId:String):void {
			
			if(authenticated) {
				
				service.addEventListener(ResultEvent.RESULT, resultOK);
				service.addEventListener(FaultEvent.FAULT, fault);
				
				var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + GDATA_PROJECTION_API 
								+ GDATA_USER_PREFFICS + authUserName;

				url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
				
                service.method = HTTPRequestMessage.DELETE_METHOD;
                
                service.headers["Authorization"]  = "GoogleLogin " + AUTH;
		                
                try {
                    service.send();
                } catch (error:Error) {
                    dispatchEvent(new GDataEvent(GDataEvent.UNABLE_TO_SEND));
                }
			} else {
				throw new Error(GDataEvent.NOT_AUTHENTICATED)
			}
			
			// kinda hack to have reference to GDataServices :)
			var eventDispatcher:* = this;

			function resultOK(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);

				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.ALBUM_DELETED));
			}
			
			function fault(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);
								
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.IO_ERROR));
			}
		}
		
		/** Update properties of the album 
		 * TODO: Implement the function*/
		private function updateAlbum(albumName:String):Boolean {
			return false;
		}

		/** Update properties of the photo 
		 * TODO: Implement the function*/		
		private function uploadPhoto(photoDEscriptor:GEntry):Boolean {
			return false;
		}
		
		private function deletePhoto(photoDEscriptor:GEntry):Boolean {
			return false;
		}
		
		private function editPhoto(photoDEscriptor:GEntry):Boolean {
			return false;
		}
		
		/**
		 * Adds comment to posted photo. Checks if user authenticated, if not throws <code>GDATA_EVENT_NOT_AUTHENTICATED</code> Exception
		 *  
		 * @param albumId - album ID
		 * @param photoId - current photo ID
		 * @param comment - comment text
		 * 
		 */
		public function addComment(albumId:String, photoId:String,comment:String):void {
	
			if(authenticated) {
				
				service.addEventListener(ResultEvent.RESULT, resultOK);
				service.addEventListener(FaultEvent.FAULT, fault);
				
				var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + GDATA_PROJECTION_API 
								+ GDATA_USER_PREFFICS + userName 
								+ GDATA_ALBUMID_PREFFICS 
								+ albumId + GDATA_PHOTOID_PREFFICS + photoId;
				
				url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
                
				service.method = HTTPRequestMessage.POST_METHOD;
                
                service.headers["Authorization"]  = "GoogleLogin " + AUTH;
		                
                try {
                    service.send(GDataGenerator.creatCommentAtomDescriptorXML(comment));
                } catch (error:Error) {
                    dispatchEvent(new GDataEvent(GDataEvent.UNABLE_TO_SEND));
                }
			} else {
				throw new Error(GDataEvent.NOT_AUTHENTICATED)
			}
			// kinda hack to have reference to GDataServices :)
			var eventDispatcher:* = this;
			
			function resultOK(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.COMMENT_ADDED));
			}
			
			function fault(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);

				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.IO_ERROR));
			}
		}
		
		/** Function to delete already posted comment.
		 * 	Throws <code>GDATA_EVENT_NOT_AUTHENTICATED</code> If user not authenticated
		 * 
		 * @param albumId - album where comment added
		 * @param photoId - photo where comemnt added
		 * @param commentId - id of comment to delete
		 * 
		 * <code>GDataService.userName</code> shoul be set to user who owns the album
		 * 
		 */
		public function deleteComment(albumId:String, photoId:String,commentId:String):void {
			if(authenticated) {
				
				service.addEventListener(ResultEvent.RESULT, resultOK);
				service.addEventListener(FaultEvent.FAULT, fault);
				
				var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + GDATA_PROJECTION_API 
								+ GDATA_USER_PREFFICS + userName 
								+ GDATA_ALBUMID_PREFFICS 
								+ albumId + GDATA_PHOTOID_PREFFICS + photoId
								+ GDATA_COMMENTID_PREFFICS + commentId;

				url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
				
                service.method = HTTPRequestMessage.DELETE_METHOD;
                
                service.headers["Authorization"]  = "GoogleLogin " + AUTH;
		                
                try {
                    service.send();
                } catch (error:Error) {
                    dispatchEvent(new GDataEvent(GDataEvent.UNABLE_TO_SEND));
                }
			} else {
				throw new Error(GDataEvent.NOT_AUTHENTICATED)
			}

			// kinda hack to have reference to GDataServices :)
			var eventDispatcher:* = this;
			
			function resultOK(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);

				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.COMMENT_DELETED));
			}
			
			function fault(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, resultOK);
				service.removeEventListener(FaultEvent.FAULT, fault);
								
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.IO_ERROR));
			}
		}
		
		/**
		 * Function requests a list of photos of a maximum of maxResults for current user.

		 * Fires <code>GET_ALBUMS</code> event in case data available
		 * and <code>NO_DATA_AVAILABLE</code> in case parameters are wrong and no data available 
		 * and <code>GET_ALBUMS_FAILED</code> in case of communication error
   
		 * @param maxResults - specifies maximum number of entries returned
		 * 
		 * */		
		public function getRecentlyUploaded(maxResults:int = -1,fields:String=null):void {
			
			var url:String = (proxied?GDATA_SERVICE_PROXY_URL:"") + GDATA_SERVICE_URL + projection 
							+ GDATA_USER_PREFFICS + userName + "/" + GDATA_PHOTO_KIND +
							(fields==null?"":apm + GDATA_FIELDS_PARAMETER + "=" + fields)
							+ (maxResults==-1?"":apm + GDATA_MAXRESULTS_PARAMETER + "=" + maxResults);

			url += apm + GDATA_VERSION_URI + "=" + GDATA_VERSION_TWO;
											
			service.url = url;
							
			service.method = HTTPRequestMessage.GET_METHOD;
			
			service.resultFormat = "e4x";
			
			service.addEventListener(ResultEvent.RESULT, onGetAlbumsForCurrentUserResult);
			service.addEventListener(FaultEvent.FAULT, onGetAlbumsForCurrentUserFailed);			
			
			service.send();
			
			var eventDispatcher:* = this;
			
			function onGetAlbumsForCurrentUserResult(event:ResultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetAlbumsForCurrentUserResult);
				service.removeEventListener(FaultEvent.FAULT, onGetAlbumsForCurrentUserFailed);
				
				var result:XML = XML(event.result);
				var albums:ArrayCollection = GDataHelper.parseAlbums(result);
				
				if(albums) {
					var gdataEvent:GDataEvent = new GDataEvent(GDataEvent.GET_ALBUMS,albums);
					eventDispatcher.dispatchEvent(gdataEvent);
				}else {
					eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.NO_DATA_AVAILABLE));
				}
			}

			function onGetAlbumsForCurrentUserFailed(event:FaultEvent):void {
				service.removeEventListener(ResultEvent.RESULT, onGetAlbumsForCurrentUserResult);
				service.removeEventListener(FaultEvent.FAULT, onGetAlbumsForCurrentUserFailed);
				
				eventDispatcher.dispatchEvent(new GDataEvent(GDataEvent.GET_ALBUMS_FAILED,event));
			}
		}
	}
}